/*
 * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.pig4cloud.pig.ads.controller;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.pig4cloud.pig.ads.config.SdkProperties;
import com.pig4cloud.pig.ads.service.*;
import com.pig4cloud.pig.api.dto.BudgetDto;
import com.pig4cloud.pig.api.entity.*;
import com.pig4cloud.pig.api.enums.StatusEnum;
import com.pig4cloud.pig.api.util.Page;
import com.pig4cloud.pig.api.vo.*;
import com.pig4cloud.pig.api.vo.AdAccountAgentVo.Advertiser;
import com.pig4cloud.pig.common.core.constant.enums.PlatformTypeEnum;
import com.pig4cloud.pig.common.core.util.R;
import com.pig4cloud.pig.common.log.annotation.SysLog;
import com.pig4cloud.pig.common.security.util.SecurityUtils;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * @ 广告账户 前端控制器
 * @author john
 *
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/advertising")
@Api(value = "advertising", tags = "广告账户管理模块")
public class AdvController {

	private static final DateTimeFormatter DATE_FORMATTER = new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd").toFormatter();

	private final AdvService advService;

	private final UserInfoService userInfoService;

	private final AdUserAdverService adUserAdverService;

	private final DailyStatService dailyStatService;

	private final AdverDayReportService adverDayReportService;

	private final AdAdverAuthService adAdverAuthService;

	private final TtAccesstokenService ttAccesstokenService;

	private final StringRedisTemplate stringRedisTemplate;

	private final TtPlanCostWarningService ttPlanCostWarningService;

	private final AdAccountService adAccountService;

	private final AdAgentService adAgentService;

	private final SdkProperties sdkProperties;

	private final AdOauthSettingService adOauthSettingService;


	@Value(value = "${app_id}")
	private String app_id;

	@Value(value = "${app_secret}")
	private String app_secret;

	@Value(value = "${app_state}")
	private String app_state;

	@Value(value = "${accesstoken_url}")
	private String accesstokenUrl;

	@Value(value = "${auth_url}")
	private String authUrl;

	@Value(value = "${auth_rediect_url}")
	private String authRediectUrl;

	@Value(value = "${auth_scope}")
	private String authScope;

	@Value(value = "${gdt_auth_url}")
	private String gdtAuthUrl;

	@Value(value = "${gdt_account_type}")
	private String gdtAccountType;

	@Value(value = "${gdt_account_display_number}")
	private String gdtAccountDisplayNumber;

	@Value(value = "${gdt_client_id}")
	private String gdtClientId;

	@Value(value = "${gdt_rediect_url}")
	private String gdtRediectUrl;

	@Value(value = "${gdt_auth_scope}")
	private String gdtAuthScope;
	public static String OCEANENGINE_OPENAPI_STATE_SPLIT = "\\|";


	/**
	 * 分页查询广告账户信息
	 * @param page 分页对象
	 * @return 分页对象
	 */
	@GetMapping("/page")
	public R getAdverPage(Page page, String advertiserId) {

		Integer id = SecurityUtils.getUser().getId();

		List<String> adList = advService.getOwnerAdv(id, PlatformTypeEnum.TT.getValue());
		if(adList == null || adList.size() == 0) {
			return R.ok(new Page());
		}
		IPage resultPage=advService.page(page, Wrappers.<Advertising>query().lambda().and(wrapper -> wrapper.in(Advertising::getAdvertiserId, adList).or().in(Advertising::getHousekeeper, adList)).and(StringUtils.isNotBlank(advertiserId), wrapper -> wrapper.eq(Advertising::getAdvertiserId, advertiserId).or().like(Advertising::getName, advertiserId)));
		AdvertisingCountVo advertisingCountVo=new AdvertisingCountVo();
		if(CollectionUtils.isNotEmpty(resultPage.getRecords())){
			List<Advertising> advertisingList=advService.list(Wrappers.<Advertising>query().lambda().and(wrapper -> wrapper.in(Advertising::getAdvertiserId, adList).or().in(Advertising::getHousekeeper, adList)).and(StringUtils.isNotBlank(advertiserId), wrapper -> wrapper.eq(Advertising::getAdvertiserId, advertiserId).or().like(Advertising::getName, advertiserId)));
			advertisingCountVo.setBalance(advertisingList.stream().filter(a->null != a.getBalance()).map(Advertising::getBalance).reduce((a,b)->a.add(b)).orElse(new BigDecimal(0)));
			advertisingCountVo.setValidBalance(advertisingList.stream().filter(a->null != a.getValidBalance()).map(Advertising::getValidBalance).reduce((a,b)->a.add(b)).orElse(new BigDecimal(0)));
			advertisingCountVo.setCash(advertisingList.stream().filter(a->null != a.getCash()).map(Advertising::getCash).reduce((a,b)->a.add(b)).orElse(new BigDecimal(0)));
			advertisingCountVo.setValidCash(advertisingList.stream().filter(a->null != a.getValidCash()).map(Advertising::getValidCash).reduce((a,b)->a.add(b)).orElse(new BigDecimal(0)));
			advertisingCountVo.setGrant(advertisingList.stream().filter(a->null != a.getGrant()).map(Advertising::getGrant).reduce((a,b)->a.add(b)).orElse(new BigDecimal(0)));
			advertisingCountVo.setValidGrant(advertisingList.stream().filter(a->null != a.getValidGrant()).map(Advertising::getValidGrant).reduce((a,b)->a.add(b)).orElse(new BigDecimal(0)));
		}
		List<AdvertisingCountVo> list = new ArrayList<>();
		list.add(advertisingCountVo);

		resultPage.setRecords(list);
		return R.ok(resultPage);
	}


	/**
	  *  查询自身账号列表
	 */
	@GetMapping("/list")
	public R getAdverList(String advertiserId) {
		List<Advertising> list = new ArrayList<>();
		Integer id = SecurityUtils.getUser().getId();

		List<String> adList = advService.getOwnerAdv(id, PlatformTypeEnum.TT.getValue());
		if(adList == null || adList.size() == 0) {
			return R.ok(list);
		}
		return R.ok(advService.list(Wrappers.<Advertising>query().lambda().and(wrapper -> wrapper.in(Advertising::getAdvertiserId, adList).or().in(Advertising::getHousekeeper, adList)).and(StringUtils.isNotBlank(advertiserId), wrapper -> wrapper.eq(Advertising::getAdvertiserId, advertiserId).or().like(Advertising::getName, advertiserId))));
	}

	@GetMapping("/upt_alert_balance")
	public R updateAlertBalance(@Valid AdvertisingWarningVO vo) {
		String advertiserId = vo.getAdvertiserId();
		BigDecimal alertBalance = vo.getAlertBalance();
		Advertising adver = advService.getById(advertiserId);
		if (adver == null) {
		    return R.failed("不存在该账户");
		}

		TtPlanCostWarning entity = new TtPlanCostWarning();
		entity.setAlertBalance(alertBalance);
		entity.setAdvertiserId(advertiserId);
//		目前此版本不支持
//		UpdateWrapper<TtPlanCostWarning> updateWrapper = new UpdateWrapper<>();
//		updateWrapper.eq("advertiser_id", entity.getAdvertiserId()).set("alert_balance", entity.getAlertBalance());
//		return  R.ok(ttPlanCostWarningService.saveOrUpdate(entity, updateWrapper));
		return  R.ok(ttPlanCostWarningService.saveOrUpdate(entity));
	}


	/**
	 *  查询自身账号列表（带管家账户）
	 */
	@GetMapping("/selectList")
	public R getSelectList(String advertiserId) {
		List<AdAccount> adAccountList = adAccountService.list(Wrappers.<AdAccount>query().lambda().and(wrapper -> wrapper.eq(AdAccount::getMediaCode, PlatformTypeEnum.TT.getValue()))
				.and(StringUtils.isNotBlank(advertiserId), wrapper -> wrapper.eq(AdAccount::getAdvertiserId, advertiserId).or().like(AdAccount::getAdvertiserName, advertiserId))
				.and(wrapper -> wrapper.apply("FIND_IN_SET ("+SecurityUtils.getUser().getId()+", throw_user)")));
		return R.ok(adAccountList);
	}


	/**
	 *  根据媒体code查询投放账号信息接口
	 */
	@GetMapping("/getMediaList")
	public R getMediaList(String mediaCode) {
		List<AdAccount> adAccountList = adAccountService.list(Wrappers.<AdAccount>query().lambda().and(wrapper -> wrapper.eq(AdAccount::getMediaCode, mediaCode))
				.and(wrapper -> wrapper.apply("FIND_IN_SET ("+SecurityUtils.getUser().getId()+", throw_user)")));
		return R.ok(adAccountList);
	}


	public static List<AdAdvSelectVo> getNewList(List<AdAdvSelectVo> oldList) {
		HashMap<String, TreeSet<AdAdvSelectVo>> tempMap = new HashMap<String, TreeSet<AdAdvSelectVo>>();

		// 去掉重复的key
		for (AdAdvSelectVo oldvo : oldList) {
			String advid = oldvo.getAdvertiserId();
			// containsKey(Object key)该方法判断Map集合中是否包含指定的键名，如果包含返回true，不包含返回false
			// containsValue(Object
			// value)该方法判断Map集合中是否包含指定的键值，如果包含返回true，不包含返回false
			if (tempMap.containsKey(advid)) {
				AdAdvSelectVo newvo = new AdAdvSelectVo();
				newvo.setAdvertiserId(advid);
				// 合并相同key的value
				newvo.getChildrenList().addAll(tempMap.get(advid));
				newvo.getChildrenList().addAll(oldvo.getChildrenList());
				// HashMap不允许key重复，当有key重复时，前面key对应的value值会被覆盖
				tempMap.put(advid, newvo.getChildrenList());
			} else {
				tempMap.put(advid, oldvo.getChildrenList());
			}
		}
		List<AdAdvSelectVo> newList = new ArrayList<AdAdvSelectVo>();
		for (Map.Entry<String, TreeSet<AdAdvSelectVo>> entry : tempMap.entrySet()) {
			AdAdvSelectVo newvo = new AdAdvSelectVo();
			newvo.setAdvertiserId(entry.getKey());
			newvo.setChildrenList(entry.getValue());
			newList.add(newvo);
        }
		return newList;
	}


	/**
	 * 广告账号授权
	 */
	@GetMapping("/auth")
	public R adverAuthPage(AdAdverAuth adAuth) {
		String adAccount = adAuth.getAdvertiserId();
		if(StringUtils.isBlank(adAccount)) {
			return R.failed("请输入需要绑定的广告账户ID");
		}
		Advertising advertising = advService.getById(adAccount);
		//不可申请管家账户
		//TtAccesstoken ttAccesstoken = ttAccesstokenService.getById(adAccount);
		if(advertising == null) {
			return R.failed("账号不存在，请联系管理员");
		}
		Integer userId = SecurityUtils.getUser().getId();
		AdUserAdver userAdver = adUserAdverService.getOne(Wrappers.<AdUserAdver>query().lambda().eq(AdUserAdver::getAdvertiserId, adAccount)
				.eq(AdUserAdver::getUserId, userId).eq(AdUserAdver::getPlatformId, PlatformTypeEnum.TT.getValue()));
		if(userAdver != null) {
			return R.failed("该广告账户ID已绑定，请勿重复绑定");
		}

		List<AdAdverAuth> list = adAdverAuthService.list(Wrappers.<AdAdverAuth>query().lambda().eq(AdAdverAuth::getAdvertiserId, adAccount)
				.eq(AdAdverAuth::getCreateuser, userId).eq(AdAdverAuth::getPlatformId, PlatformTypeEnum.TT.getValue()).eq(AdAdverAuth::getStatus, 0));
		if(list != null && list.size() > 0) {
			return R.failed("该广告账户ID还在申请授权中，请勿重复授权");
		}


		adAuth.setPlatformId(PlatformTypeEnum.TT.getValue());
		adAuth.setCreateuser(userId);
		return R.ok(adAdverAuthService.save(adAuth));
	}



	/**
	 * 加挂
	 * https://ad.oceanengine.com/openapi/audit/oauth.html?app_id={0}&state={1}&scope={2}&redirect_uri={3}
	 * platformId 1:头条 8：广点通 10：快手
	 */
	@GetMapping("/toauth")
	public R toauth(@RequestParam String platformId, @RequestParam(required = false) Integer agentId) {
		if (StringUtils.isBlank(platformId)) {
			return R.failed("平台ID不能为空");
		}
		//获取所有回调配置
		List<AdOauthSetting> oauthSettingList = adOauthSettingService.list(Wrappers.<AdOauthSetting>lambdaQuery().eq(AdOauthSetting::getDeleted,0));
		if (CollectionUtils.isEmpty(oauthSettingList)) {
			return R.failed("开发者应用信息未配置，请联系技术人员。");
		}
		Map<String,AdOauthSetting> oauthSettingMap = oauthSettingList.stream()
				.collect(Collectors.toMap(AdOauthSetting::getMediaCode, Function.identity()));
		AdOauthSetting setting = oauthSettingMap.get(platformId);
		if (setting == null || StringUtils.isBlank(setting.getAuthUrl())) {
			return R.failed(PlatformTypeEnum.descByValue(platformId)+"渠道开发者应用信息未配置，请联系技术人员。");
		}
		Integer userId = Objects.requireNonNull(SecurityUtils.getUser()).getId();
		agentId = null == agentId ? Integer.MAX_VALUE : agentId;
		String authUrl = setting.getAuthUrl();
		if (platformId.equals(PlatformTypeEnum.TT.getValue())) {
			// 加挂头条管家账户
			String realAuthUrl = authUrl.replace("{0}", setting.getAppId())
					.replace("{1}", String.format("%s|%d|%d", setting.getAppState(), userId, agentId))
					.replace("{2}", urlEncode("[" + setting.getAppScope() + "]"))
					.replace("{3}", urlEncode(setting.getRedirectUri()));
			return R.ok(realAuthUrl);
		} else if (platformId.equals(PlatformTypeEnum.GDT.getValue())) {
			// 加挂广点通管家账户，广点通不支持"|"作为url参数
			final String state = Base64.getUrlEncoder().encodeToString(String.format("|%d|%d", userId, agentId).getBytes(StandardCharsets.UTF_8));
			String realAuthUrl = authUrl.replace("{0}", setting.getAppId())
					.replace("{1}", state)
					.replace("{2}", setting.getAppScope())
					.replace("{3}", setting.getOauthType())
					.replace("{4}", setting.getColumn1())
					.replace("{5}", urlEncode(setting.getRedirectUri()));
			return R.ok(realAuthUrl);
		} else if (platformId.equals(PlatformTypeEnum.KS.getValue())) {
			// 快手 scop 和 redirect 只能编码不能转base64
			String encoderScope = urlEncode(setting.getAppScope());
			String enCoderRedirectUri = urlEncode(setting.getRedirectUri());
			Map<String,Object> stateMap = new HashMap<>();
			stateMap.put("userId",userId);
			stateMap.put("agentId",agentId);
			String enCoderState = Base64.getUrlEncoder().encodeToString(JSON.toJSONString(stateMap).getBytes(StandardCharsets.UTF_8));
			String realAuthUrl = MessageFormat.format(authUrl,setting.getAppId(),encoderScope,enCoderRedirectUri,setting.getOauthType(),enCoderState);
			return R.ok(realAuthUrl);
		} else if (platformId.equals(PlatformTypeEnum.BD.getValue())) {
			String enCoderRedirectUri = urlEncode(setting.getRedirectUri());
			Map<String,Object> stateMap = new HashMap<>();
			stateMap.put("userId",userId);
			stateMap.put("agentId",agentId);
			String enCoderState = Base64.getUrlEncoder().encodeToString(JSON.toJSONString(stateMap).getBytes(StandardCharsets.UTF_8));
			String realAuthUrl = MessageFormat.format(authUrl,
					setting.getAppId(),
					setting.getAppScope(),
					enCoderState,
					enCoderRedirectUri);
			return R.ok(realAuthUrl);
		}
		return R.failed();
	}

	private String urlEncode(String str) {
		try {
			return URLEncoder.encode(str, "UTF-8");
		}
		catch (UnsupportedEncodingException e) {
			return null;
		}
	}


	/**
	  *  头条授权地址appid, secret目前配死 , auth_code通过回调地址返回回来
	  *  请求地址必须包含appid, state
	  *  拼接授权链接时自定义拼装的参数，回调时原样返回
	  *  一些常见用法：
	 * 1. 校验是否是指定的授权（合理性）
	 * 2. 特殊逻辑处理（指定的客户需要根据一些参数进行特殊的处理，state=need_transfer）
	 * 3. 存储指定信息（例如希望给客户存储公司信息，在引导用户授权拼接授权url时设置state=company_name）
	 */
	@GetMapping("/audit")
	public R audit(AuthCodeReqVo authVo) {
		String stateVo = authVo.getState();
		String authCode = authVo.getAuth_code();
		if(StringUtils.isAnyBlank(stateVo, authCode)) {
			return R.failed( "请求地址返回不合法");
		}

		String[] args = stateVo.split(OCEANENGINE_OPENAPI_STATE_SPLIT);
		//无需校验
//    	if(args.length != 3 || !args[0].equals(app_state)){
//    		return R.failed( "授权URL配置state错误!");
//    	}
    	Integer userId = Integer.parseInt(args[1]);
    	Integer agentId = Integer.parseInt(args[2]);

		authVo.setApp_id(app_id);
		authVo.setSecret(app_secret);
		authVo.setUserId(userId);
		List<Advertiser> advertisers = ttAccesstokenService.saveAuth(authVo);

		// 为广告账户设置代理
		AdAgent agent = adAgentService.getOne(Wrappers.<AdAgent>lambdaQuery().select(AdAgent::getAgentName).eq(AdAgent::getId, agentId).eq(AdAgent::getIsDelete, 0));
		if (null == agent) {
			return R.ok(null,"头条授权成功");
		}
		AdAccountAgentVo adAccountAgentVo = new AdAccountAgentVo();
		adAccountAgentVo.setAgentId(args[2]);
		adAccountAgentVo.setAgentName(agent.getAgentName());
		adAccountAgentVo.setEffectiveTime(LocalDate.now().format(DATE_FORMATTER));
		adAccountAgentVo.setAdvertisers(advertisers);
		List<String> list = adAccountService.batchAddAccountAgent(adAccountAgentVo, userId);

		return R.ok(list);
	}






	@RequestMapping("/userinfo")
	public R userinfo() {
		UserInfo userInfo = userInfoService.selectById(1);
		return R.ok(userInfo);
	}

	/**
	 * 修改日账户预算
	 * @param  budgetDto
	 * @return success/false
	 */
	@SysLog("修改账号日预算")
	@RequestMapping("/budgetUpdate")
//	@PreAuthorize("@pms.hasPermission('ad_budget_update')")
	public R budgetUpdate(BudgetDto budgetDto) {
		return advService.budgetUpdate(budgetDto);
	}

	/**
	 * 查询广告账号预约当天定时设置预算值
	 * @param  advertiserId
	 * @return success/false
	 */
	@SysLog("查询该账户设置的日预算")
	@RequestMapping("/getAdvertiserBudget")
	public R getAdvertiserBudget(Long advertiserId) {
		return advService.getAdvertiserJobBudget(advertiserId,null,null, StatusEnum.OPERATE_TYPE_ONE.getStatus());
	}




}
